<!DOCTYPE html><html>

<head>
<meta charset="utf-8">
<title>PM25.io | OpenSource Node Server Monitoring Platform</title>
<style type="text/css">
body {
  font-family: "Hiragino Kaku Gothic ProN", "Helvetica Neue", "Luxi Sans", "DejaVu Sans", Tahoma, "Hiragino Sans GB", STHeiti;
  font-size: 15px;
  line-height: 1.6;
  padding-top: 10px;
  padding-bottom: 10px;
  background-color: white;
  padding: 30px; }

body > *:first-child {
  margin-top: 0 !important; }
body > *:last-child {
  margin-bottom: 0 !important; }

a {
  color: #4183C4; }
a.absent {
  color: #cc0000; }
a.anchor {
  display: block;
  padding-left: 30px;
  margin-left: -30px;
  cursor: pointer;
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0; }

h1, h2, h3, h4, h5, h6 {
  margin: 20px 0 10px;
  padding: 0;
  font-weight: bold;
  -webkit-font-smoothing: antialiased;
  cursor: text;
  position: relative; }

h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, h5:hover a.anchor, h6:hover a.anchor {
  background: url() no-repeat 10px center;
  text-decoration: none; }

h1 tt, h1 code {
  font-size: inherit; }

h2 tt, h2 code {
  font-size: inherit; }

h3 tt, h3 code {
  font-size: inherit; }

h4 tt, h4 code {
  font-size: inherit; }

h5 tt, h5 code {
  font-size: inherit; }

h6 tt, h6 code {
  font-size: inherit; }

h1 {
  font-size: 28px;
  color: black; }

h2 {
  font-size: 24px;
  border-bottom: 1px solid #cccccc;
  color: black; }

h3 {
  font-size: 18px; }

h4 {
  font-size: 16px; }

h5 {
  font-size: 14px; }

h6 {
  color: #777777;
  font-size: 14px; }

p, blockquote, ul, ol, dl, li, table, pre {
  margin: 15px 0; }

hr {
  background: transparent url() repeat-x 0 0;
  border: 0 none;
  color: #cccccc;
  height: 4px;
  padding: 0;
}

body > h2:first-child {
  margin-top: 0;
  padding-top: 0; }
body > h1:first-child {
  margin-top: 0;
  padding-top: 0; }
  body > h1:first-child + h2 {
    margin-top: 0;
    padding-top: 0; }
body > h3:first-child, body > h4:first-child, body > h5:first-child, body > h6:first-child {
  margin-top: 0;
  padding-top: 0; }

a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 {
  margin-top: 0;
  padding-top: 0; }

h1 p, h2 p, h3 p, h4 p, h5 p, h6 p {
  margin-top: 0; }

li p.first {
  display: inline-block; }
li {
  margin: 0; }
ul, ol {
  padding-left: 30px; }

ul :first-child, ol :first-child {
  margin-top: 0; }

dl {
  padding: 0; }
  dl dt {
    font-size: 14px;
    font-weight: bold;
    font-style: italic;
    padding: 0;
    margin: 15px 0 5px; }
    dl dt:first-child {
      padding: 0; }
    dl dt > :first-child {
      margin-top: 0; }
    dl dt > :last-child {
      margin-bottom: 0; }
  dl dd {
    margin: 0 0 15px;
    padding: 0 15px; }
    dl dd > :first-child {
      margin-top: 0; }
    dl dd > :last-child {
      margin-bottom: 0; }

blockquote {
  border-left: 4px solid #dddddd;
  padding: 0 15px;
  color: #777777; }
  blockquote > :first-child {
    margin-top: 0; }
  blockquote > :last-child {
    margin-bottom: 0; }

table {
  padding: 0;border-collapse: collapse; }
  table tr {
    border-top: 1px solid #cccccc;
    background-color: white;
    margin: 0;
    padding: 0; }
    table tr:nth-child(2n) {
      background-color: #f8f8f8; }
    table tr th {
      font-weight: bold;
      border: 1px solid #cccccc;
      margin: 0;
      padding: 6px 13px; }
    table tr td {
      border: 1px solid #cccccc;
      margin: 0;
      padding: 6px 13px; }
    table tr th :first-child, table tr td :first-child {
      margin-top: 0; }
    table tr th :last-child, table tr td :last-child {
      margin-bottom: 0; }

img {
  max-width: 100%; }

span.frame {
  display: block;
  overflow: hidden; }
  span.frame > span {
    border: 1px solid #dddddd;
    display: block;
    float: left;
    overflow: hidden;
    margin: 13px 0 0;
    padding: 7px;
    width: auto; }
  span.frame span img {
    display: block;
    float: left; }
  span.frame span span {
    clear: both;
    color: #333333;
    display: block;
    padding: 5px 0 0; }
span.align-center {
  display: block;
  overflow: hidden;
  clear: both; }
  span.align-center > span {
    display: block;
    overflow: hidden;
    margin: 13px auto 0;
    text-align: center; }
  span.align-center span img {
    margin: 0 auto;
    text-align: center; }
span.align-right {
  display: block;
  overflow: hidden;
  clear: both; }
  span.align-right > span {
    display: block;
    overflow: hidden;
    margin: 13px 0 0;
    text-align: right; }
  span.align-right span img {
    margin: 0;
    text-align: right; }
span.float-left {
  display: block;
  margin-right: 13px;
  overflow: hidden;
  float: left; }
  span.float-left span {
    margin: 13px 0 0; }
span.float-right {
  display: block;
  margin-left: 13px;
  overflow: hidden;
  float: right; }
  span.float-right > span {
    display: block;
    overflow: hidden;
    margin: 13px auto 0;
    text-align: right; }

code, tt {
  margin: 0 2px;
  padding: 0 5px;
  white-space: nowrap;
  border: 1px solid #eaeaea;
  background-color: #f8f8f8;
  border-radius: 3px; }

pre code {
  margin: 0;
  padding: 0;
  white-space: pre;
  border: none;
  background: transparent; }

.highlight pre {
  background-color: #f8f8f8;
  border: 1px solid #cccccc;
  font-size: 13px;
  line-height: 19px;
  overflow: auto;
  padding: 6px 10px;
  border-radius: 3px; }

pre {
  background-color: #f8f8f8;
  border: 1px solid #cccccc;
  font-size: 13px;
  line-height: 19px;
  overflow: auto;
  padding: 6px 10px;
  border-radius: 3px; }
  pre code, pre tt {
    background-color: transparent;
    border: none; }

sup {
    font-size: 0.83em;
    vertical-align: super;
    line-height: 0;
}
* {
	-webkit-print-color-adjust: exact;
}
@media screen and (min-width: 914px) {
    body {
        width: 854px;
        margin:0 auto;
    }
}
@media print {
	table, pre {
		page-break-inside: avoid;
	}
	pre {
		word-wrap: break-word;
	}
}
</style>
</head>
<body>

<style>
#github img {
  position: fixed;
  top: 0;
  right: 0;
  border: 0;
}
</style>
<img src="images/pm25-logo-latest.png" alt="" style="margin: 40px 0 30px!important;">
<a href="http://github.com/PaulGuo/PM25" id="github">
  <img alt="Fork me on GitHub" src="http://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png">
</a>

<h3 id="toc_0">NodeJS服务监控报警系统的核心实现和开源共建</h3>

<ol>
<li>建设服务监控系统的初衷和历程</li>
<li>核心实现思路及实际应用</li>
<li>现有功能点介绍和路线图</li>
<li>代码开源的目的、愿景</li>
<li>开源代码的结构和部署拓扑图</li>
<li>项目开发者的个人介绍</li>
</ol>

<h4 id="toc_1">建设服务监控系统的初衷和历程</h4>

<p>随着基于NodeJS前后端分离方案的推行，前端的开发模式和角色也在发生着悄无声息的变化，而今NodeJS的开发俨然已经成为我们日常工作中的一部分，前端工程师与服务端、运维都有了更多的交集，但随着业务和项目的扩张，生产环境Node服务也在不断增多，如何对这些服务的运行状态和各项指标了如指掌是当前我们大家共同遇到的挑战。</p>

<p>我的初衷是建立一个专门针对NodeJS的服务监控平台，它能够支持服务集群的统一管理和多用户登录，旨在帮助团队的开发者、架构人员以及管理者能够直观的观察线上各个服务的实时状态，并能够帮助开发者及时发现线上服务的异常情况（比如内存泄露、服务崩溃重启、慢路由等）。</p>

<p>PM2是一款非常优秀的Node进程管理工具，它有着丰富的特性：能够充分利用多核CPU且能够负载均衡、能够帮助应用在崩溃后自动重启、能够监控资源的使用情况并且支持API方式查看、并且有配套的Keymetrics可以用于服务的监控。相信不少开发者都在使用PM2部署自己的NodeJS应用，起初也曾尝试使用Keymetrics，但Keymetrics是一款商业服务且价格不菲，虽有两台服务器的免费配额但对于有着众多服务器的团队或者公司而言无异于杯水车薪，而且对于国内的大公司而言都会考虑到自身的数据敏感性不希望服务的运行状态接入到第三方平台中，当然如果你的服务器数量很少，又或者能够支付昂贵的使用费用，而且无需关心数据的安全问题依旧推荐可以使用Keymetrics，毕竟它是PM2的开发者的开发和维护，而且功能特性也很丰富。</p>

<p>但我遇到的场景是如何实现一套通用的监控系统，它能够独立部署而且方便进行扩展和定制化，显然在现阶段使用Keymetrics不是十分明智的决定，于是便想到利用PM2的API进行服务监控，但API的方式不利于多台机器的统一管理和聚合，而且通过API的方式会将服务的详细信息泄露到外网，有一定的安全隐患。之后便开始设想是否能够基于PM2进行一些二次开发，实现一个核心功能并具备一定定制性的系统（类似Keymetrics），一方面可以为整个行业的Node推广起到一定的积极作用，一方面能够确实帮助到我们团队的服务运维。我将这个项目命名为PM25，意即基于PM2的基础设施多做了一点，仅此而已别无他意。</p>

<h4 id="toc_2">现有功能点介绍和路线图</h4>

<ul>
<li>支持用户的管理（登录作为中间件）</li>
<li>被监控机器的分桶管理</li>
<li>机器列表、快速过滤和主机的指标信息（进程数、CPU数、负载、上线时长、内存占用）</li>
<li>进程的详细指标信息（PID、进程名、重启次数、上线时长、状态、CPU占用、内存占用、错误日志）</li>
<li>同Falcon整合，支持监控报警管理（核心指标同步Falcon，可以查看历史图表或者配置监控报警）</li>
<li>支持扩展包，引入扩展包后可以收集统计服务端慢路由信息</li>
<li>支持进程的远程控制，可以在云端对进程进行远程操作（比如重启、重载）</li>
</ul>

<h4 id="toc_3">代码开源的目的、愿景</h4>

<p>我的初衷很简单，一方面能够把自己在工作之余的贡献拿出来和行业分享，也希望绵薄之力能够对行业的Node推广起到一些作用，反过来也希望行业内的济济人才能够参与到它的建设中来，基于我目前的成果和代码进行更多的功能开发和细节完善，把大家对Node的热忱和对PM25的建设形成合力为行业带来一些影响或是价值，代码托管于<a href="https://github.com/PaulGuo/PM25">Github</a>，项目地址为<a href="http://pm25.io">PM25.io</a>。</p>

<h4 id="toc_4">开源代码的组成结构</h4>

<pre><code>├── PM2.5-Cli           // PM25 CLI
├── PM2.5-Cloud         // PM25 云端控制台
├── PM2.5-Service       // PM25 服务端代码
├── PM2.5-Docs          // PM25 文档
└── PM2.5-Ext           // PM25 扩展包</code></pre>

<h4 id="toc_5">数据库组成和分表结构</h4>

<pre><code>├── pm25                // 主库，用于监控主机的信息存储
│   ├── buckets         // 桶表，用于存储主机分桶信息
│   ├── events          // 事件表，用于存储主机的事件信息
│   ├── exceptions      // 错误异常表，用于存储主机的线上报错
│   ├── monitorings     // 监控信息表，用于存储主机的整体监控数据
│   ├── status          // 主表，用于存储主机的各个进程数据
│   └── transactions    // 网络传输表，用于存储慢路由相关信息
├── pm25-session        // 会话信息库，用于存储SSO登录后的用户会话
│   └── sessions        // 会话信息主表，用于存储用户会话详情
├── pm25-test           // 测试表，用于开发过程中的测试</code></pre>

<h4 id="toc_6">部署拓扑图</h4>

<p><img src="http://ww1.sinaimg.cn/large/62755f82jw1ez3ueeghpvj20hu0g0acc.jpg" alt=""></p>

<h4 id="toc_7">使用截图</h4>

<p><img src="http://ww1.sinaimg.cn/large/62755f82gw1ez4f6devo9j20lm08ymy7.jpg" alt=""></p>

<p><img src="http://ww1.sinaimg.cn/large/62755f82gw1ez4f6qdlrpj20ld0p8adm.jpg" alt=""></p>

<p><img src="http://ww4.sinaimg.cn/large/62755f82gw1ez4f754a1pj20ld0kcjtz.jpg" alt=""></p>

<p><img src="http://ww2.sinaimg.cn/large/62755f82gw1ez4f8zsjygj20z30km43r.jpg" alt=""></p>

<p><img src="http://ww4.sinaimg.cn/large/62755f82gw1ez4f9absxbj20qd0bcdi7.jpg" alt=""></p>

<h4 id="toc_8">项目作者的个人背景介绍</h4>

<p>郭凯，工作狂、强迫症，崇尚工匠精神，全栈工程师，翻译作品有《编写可维护的JavaScript》、《第三方JavaScript编程》。开源项目有<a href="http://injs.org">In</a>、<a href="http://juicer.name">Juicer</a>、<a href="http://jsql.us">jSQL</a>、以及开源前端技术社区<a href="http://f2e.im">F2E</a>等，欢迎关注我的<a href="http://weibo.com/janbing">微博</a>、<a href="http://paulguo.io/">个人博客</a>、或者加入<a href="http://f2e.im/guokai">前端技术社区</a>，随时同我进行技术交流。</p>

<div id="disqus_thread"></div>
<script>
/**
* RECOMMENDED CONFIGURATION VARIABLES: EDIT AND UNCOMMENT THE SECTION BELOW TO INSERT DYNAMIC VALUES FROM YOUR PLATFORM OR CMS.
* LEARN WHY DEFINING THESE VARIABLES IS IMPORTANT: https://disqus.com/admin/universalcode/#configuration-variables
*/
/*
var disqus_config = function () {
this.page.url = PAGE_URL; // Replace PAGE_URL with your page's canonical URL variable
this.page.identifier = PAGE_IDENTIFIER; // Replace PAGE_IDENTIFIER with your page's unique identifier variable
};
*/
(function() { // DON'T EDIT BELOW THIS LINE
var d = document, s = d.createElement('script');

s.src = '//pm25.disqus.com/embed.js';

s.setAttribute('data-timestamp', +new Date());
(d.head || d.body).appendChild(s);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>

<div style="display:none;">
    <script src="http://s11.cnzz.com/stat.php?id=1257001197&web_id=1257001197" language="JavaScript"></script>
    <script>
        var _hmt = _hmt || [];
        (function() {
            var hm = document.createElement("script");
            hm.src = "//hm.baidu.com/hm.js?d2115adb20973db577d81e9c0fe4603f";
            var s = document.getElementsByTagName("script")[0]; 
            s.parentNode.insertBefore(hm, s);
        })();
    </script>
</div>

</body>

</html>
